﻿using System;
using System.IO;
using System.Linq;
using System.Text;

namespace Microsoft.Media.ISO
{
    public static class BoxExtensions
    {
        public static ulong CalculateBoxSize(this IBox source)
        {
            using (var stream = new MemoryStream())
            {
#if NETFX_CORE
                using (var writer = new BoxBinaryWriter(stream, true))
#else
                var writer = new BoxBinaryWriter(stream);
#endif
                {
                    MP4BoxWriter.WriteBox(source, writer);
                    writer.Flush();
                }
                stream.Flush();
                return (ulong)stream.Length;
            }
        }

        public static AudioTrackCodecData GetAudioCodecData(this AudioSampleEntryBox source)
        {
            var result = new AudioTrackCodecData();

            var esds = source.ElementaryStreamDescriptor;
            var dcd = esds.StreamDescriptor.SubDescriptors.OfType<DecoderConfigurationDescriptor>().FirstOrDefault();
            var dsi = esds.StreamDescriptor.SubDescriptors.OfType<DecoderSpecificInformationDescriptor>().FirstOrDefault();
            if (dsi != null && dcd != null)
            {
                result.CodecPrivateData = ConvertToHex(dsi.Information);
                switch (dcd.ObjectTypeIndication)
                {
                    case DecoderObjectTypes.MPEG1_AUDIO:
                        result.PacketSize = 1;
                        result.AudioTag = 0x55;
                        break;
                    case DecoderObjectTypes.MPEG2_AAC_AUDIO_SSRP:
                    case DecoderObjectTypes.MPEG2_AAC_AUDIO_LC:
                    case DecoderObjectTypes.MPEG2_AAC_AUDIO_MAIN:
                    case DecoderObjectTypes.MPEG4_AUDIO:
                    case DecoderObjectTypes.MPEG2_PART3_AUDIO:
                        result.PacketSize = 4;
                        if (dsi.Information[0] == 0x05 || dsi.Information[0] == 0x1D)
                            result.AudioTag = 0x1610; //HE-AAC
                        else
                            result.AudioTag = 0xFF; //AAC-LC
                        break;
                    default:
                        throw new NotImplementedException();
                }
                result.FourCodecCode = MapAudioTagToFourCodecCode(result.AudioTag);
            }
            else throw new NotImplementedException();

            return result;
        }

        public static VideoTrackCodecData GetVideoCodecData(this VisualSampleEntryBox source)
        {
            var result = new VideoTrackCodecData();

            AdvancedVideoCodingBox avcc = source.InnerBoxes.OfType<AdvancedVideoCodingBox>().FirstOrDefault();
            if (avcc != null)
            {
                result.FourCodecCode = "H264";
                result.DisplayWidth = source.Width;
                result.DisplayHeight = source.Height;
                string sps = ConvertToHex(avcc.SequenceParameters.First().ToArray());
                string pps = ConvertToHex(avcc.PictureParameters.First().ToArray());
                result.CodecPrivateData = "00000001" + sps + "00000001" + pps;
            }
            else throw new NotImplementedException();

            return result;
        }

        static string MapAudioTagToFourCodecCode(ushort fourCC)
        {
            switch (fourCC)
            {
                case 0x1:
                case 0x162:
                case 0x161:
                case 0x160:
                    return "WMAP";
                case 0xFF:
                    return "AACL";
                case 0x55:
                    return "MPEG";
                case 0x1610:
                    return "AACH";
                default:
                    throw new NotImplementedException();
            }
        }

        static string ConvertToHex(byte[] bytes)
        {
            StringBuilder hex = new StringBuilder(bytes.Length * 2);
            foreach (byte b in bytes)
            {
                hex.AppendFormat("{0:x2}", b);
            }
            return hex.ToString();
        }
    }

    public sealed class AudioTrackCodecData
    {
        /// <summary>
        /// Gets the track four codec code.
        /// </summary>
        public string FourCodecCode { get; set; }
        // <summary>
        /// Gets the track codec private data.
        /// </summary>
        public string CodecPrivateData { get; set; }
        /// <summary>
        /// Gets the audio track tag.
        /// </summary>
        public ushort AudioTag { get; set; }
        /// <summary>
        /// Gets the audio track size of the packet.
        /// </summary>       
        public ushort PacketSize { get; set; }
    }

    public sealed class VideoTrackCodecData
    {
        /// <summary>
        /// Gets the video track display height.
        /// </summary>
        public uint DisplayHeight { get; set; }
        /// <summary>
        /// Gets the video track display width.
        /// </summary>
        public uint DisplayWidth { get; set; }
        /// <summary>
        /// Gets the track codec private data.
        /// </summary>
        public string CodecPrivateData { get; set; }
        /// <summary>
        /// Gets the track four codec code.
        /// </summary>
        public string FourCodecCode { get; set; }

    }
}
